package com.aqumon.www.util;

import com.aqumon.www.entity.dto.AdUserDto;
import com.aqumon.www.entity.response.ldap.LoginResp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Hashtable;
import java.util.List;

/**
 * ldap方式 对AD域用户进行增删改查
 */

public class LdapUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(LdapUtil.class);
    // 常量定义
    // jdk的ssl证书文件路径
    private static final String javaHome = System.getProperty("java.home");
    private static final String javaSSLFile =  javaHome + "/lib/security/cacerts";

    // ad域连接ssl端口号
    private final static int adSSLPort = 636;

    // 保存当前类对象
    private static DirContext ldapContext = null;
    private static LdapContext ctx;
    private static final String msg = "";
    private static final LoginResp loginResp = new LoginResp(true, msg, ctx);
    // 创建当前类对象时，自动创建连接对象，并对ldapContext和ctx赋值
    // 默认连接389端口

    /**
     * 1 连接AD域,根据用户的登录名来进行连接，和显示的名称、用户姓、用户名无关 此方式是389普通方式连接
     * @param adHost  ad域地址
     * @param port   ad域连接端口号
     * @param userDN   账户DN字段
     * @param userPassword   账户密码
     * @param timeout
     * @return LdapContext
     */
    public LoginResp getConnectNormal(String adHost, int port, String userDN, String userPassword, String timeout) {
        Hashtable<String, String> env = new Hashtable<>();
        // 引用ldap工厂模式创建对象，固定写法
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
        // 连接ad的url，格式为:ldap://adip:port,port可以是389,636,389是普通模式，可以对用户增删改查操作；636是ssl方式，可以进行用户密码修改等操作
        String ldapURL = "ldap://" + adHost + ":" + port;// 连接iP与端口号
        env.put(Context.PROVIDER_URL, ldapURL);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");// 输出网络认证实例，一般都是simple方式
        // 用户名(管理员用户) ，格式是：域名\\用户名
        if (userDN.isEmpty() || null == userDN || userPassword.isEmpty() || null == userPassword) {
            loginResp.setMsg("用户名或密码为空");
            loginResp.setRst(false);
            return loginResp;
        }
        env.put(Context.SECURITY_PRINCIPAL, userDN);// 用户名(管理员用户)
        env.put(Context.SECURITY_CREDENTIALS, userPassword);//  用户的密码
        env.put("com.sun.jndi.ldap.connect.timeout", timeout);  //设置超时
        try {
            ldapContext = new InitialDirContext(env);
            ctx = new InitialLdapContext(env, null);
            System.out.println(userDN + "连接AD域成功");
            loginResp.setMsg(userDN + "认证成功");
        } catch (AuthenticationException e) {
            loginResp.setRst(false);
            String erroMsg=  e.getMessage();
            if (erroMsg.contains("701")) {
                loginResp.setMsg("该账户已过期");
            } else if (erroMsg.contains("52e")) {
                loginResp.setMsg("用户名或密码错误");
            } else if (erroMsg.contains("525")) {
                loginResp.setMsg("用户名或密码错误");
            } else if (erroMsg.contains("773")) {
                loginResp.setMsg("用户必须重置密码");
            } else if (erroMsg.contains("533")) {
                loginResp.setMsg("用户账户禁用");
            } else {
                loginResp.setMsg("用户认证失败");
            }
        } catch (NameNotFoundException e) {
            loginResp.setRst(false);
            loginResp.setMsg("登录发生NameNotFound异常");
            e.printStackTrace();
        } catch(NamingException e) {
            loginResp.setRst(false);
            loginResp.setMsg("发生NamingException异常");
            e.printStackTrace();
        } catch (Exception e) {
            loginResp.setRst(false);
            loginResp.setMsg(userDN + "连接AD域失败");
            e.printStackTrace();
        }
        return loginResp;
    }
    /**  此方式是通过ssL方式连接
     * @param adHost   ad域IP地址
     * @param port     连接端口号
     * @param userName  用户名
     * @param passWord  密码
     * @param keyStore    ssl认证文件位置

     * @return
     */
    public static LdapContext getConnectSSL(String adHost, int port, String userName,
                                            String  passWord, String keyStore) {
        Hashtable<Object, Object> env = new Hashtable<>();
        // 加载导入jdk的域证书
        System.setProperty("javax.net.ssl.trustStore", keyStore);
        System.setProperty("javax.net.ssl.trustStorePassword",  "changeit");// ssl认证的密码
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");// 固定写法
        env.put(Context.SECURITY_PROTOCOL, "ssl");
        // 连接认证服务器
        env.put(Context.PROVIDER_URL, "ldaps://" + adHost + ":"  + port);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, userName);
        env.put(Context.SECURITY_CREDENTIALS, passWord);
        try {
            ldapContext = new InitialDirContext(env);
            ctx = new InitialLdapContext(env, null);
            System.out.println(userName.trim().split("\\\\")[1].trim()
                    + "认证成功！！！");
            return ctx;
        } catch (javax.naming.AuthenticationException e) {
            System.out.println("认证失败:" + e.getMessage());
        } catch (Exception e) {
            System.out.println("认证出错:" + e.getMessage());
        }
        return null;
    }
    /**
     * 添加组织机构
     * @param ouDN  要添加的组织机构全路径，如:  CN=Users,DC=example,DC=bdc
     * @return
     * @throws NamingException
     */
    public static boolean addOU(String ouDN) throws  NamingException {
        try {
            Attributes attrs = new BasicAttributes(true);
            attrs.put("objectClass", "organizationalUnit");
            ctx.createSubcontext(ouDN, attrs);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ctx != null) {
                ctx.close();
                ctx = null;
            }
        }
        return false;
    }
    /**
     * 遍历AD域指定节点所有用户
     * @param baseDN   要查询的组织机构全路径，如:  CN=Users,DC=example,DC=bdc
     * @return   List<String>
     * @throws NamingException
     */
    public static List<String> getUserInfo(String baseDN) throws NamingException {
        SearchControls searchCtls = new SearchControls(); // 设置搜索范围
        String[] returnedAtts = { "distinguishedName",  "sAMAccountName",
                "userPrincipalName" };
        searchCtls.setReturningAttributes(returnedAtts); // 设置指定返回的字段，不设置则默认返回全部
        ArrayList<String> userMessageList = new ArrayList<>();// 设置一个集合，用来保存用户信息
        searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);//  全部参数是OBJECT_SCOPE,ONELEVEL_SCOPE

        // 注意OU和DC的先后顺序,cn,ou,dc 分别：用户，组，域
        NamingEnumeration results = ldapContext.search(baseDN,
                "objectClass=User", searchCtls);
        while (results.hasMoreElements()) {
            SearchResult sr = (SearchResult) results.next();// 返回查到用户的详细信息
            Attributes attributes = sr.getAttributes();// 得到所需的用户信息组
            Attribute userPrincipalName =  attributes.get("userPrincipalName");// 存在null项的话，直接使用toString()会报错
            String testString = "";
            if (userPrincipalName == null) {
                testString = "null:null";
            } else {
                testString = userPrincipalName.toString();
            }
            String[] userNameArray =  attributes.get("distinguishedName")
                    .toString().split("\\:");// 分隔字符串，得到需要信息
            String[] userPrincipalNameArray = testString.split("\\:");
            String[] sAMAccountNameArray =  attributes.get("sAMAccountName")
                    .toString().split("\\:");
            String userMessage = "用户名:" + userNameArray[1] +  ",票据名:"
                    + userPrincipalNameArray[1] + ",对应登录名:"
                    + sAMAccountNameArray[1];
            userMessageList.add(userMessage);// 将返回的信息存到定义的用户list中
        }
        System.out.println("遍历成功");
        return userMessageList;
    }
    /**
     * 添加一个用户,此方式只适合通过636端口添加用户，通过389添加的用户无法设置密码
     * @param newUserName  添加的用户名
     * @param password    添加用户的密码
     * @param userPrincipalName   用户所持有的服务票据
     * @param rootDN    要添加到ad域的节点信息
     */
    public static void addUserBySSL(String newUserName, String  password,
                                    String userPrincipalName, String rootDN) {
        try {
            BasicAttributes attrs = new BasicAttributes();
            BasicAttribute objclassSet = new  BasicAttribute("objectClass");
            objclassSet.add("person");
            objclassSet.add("top");
            objclassSet.add("user");
            attrs.put(objclassSet);
            // 设置姓
            attrs.put("sn",
                    newUserName.substring(0,  newUserName.trim().length() / 2));
            attrs.put("givenName",
                    newUserName.substring(newUserName.trim().length() / 2));// 设置名
            attrs.put("CN", newUserName);// 用户名
            attrs.put("displayName", newUserName);// 显示名称
            attrs.put("description", "集群kerberos服务用户");// 用户信息描述
            attrs.put("userPrincipalName", userPrincipalName);// 和用户绑定的服务票据名
            attrs.put("sAMAccountName", newUserName); // 设置登录名
            attrs.put("msDS-SupportedEncryptionTypes", "0");
            int UF_ACCOUNTDISABLE = 0x0002;
            int UF_PASSWD_NOTREQD = 0x0020;
            int UF_PASSWD_CANT_CHANGE = 0x0040;
            int UF_NORMAL_ACCOUNT = 0x0200;
            int UF_DONT_EXPIRE_PASSWD = 0x10000;
            int UF_PASSWORD_EXPIRED = 0x800000;
            // 密码永不过期
            attrs.put(
                    "userAccountControl",
                    Integer.toString(UF_DONT_EXPIRE_PASSWD
                            + UF_PASSWD_CANT_CHANGE  + UF_PASSWD_NOTREQD));
            System.out.println("正在处理的用户为：" +  newUserName);
            Context result = ldapContext.createSubcontext("CN=" +  newUserName
                    + "," + rootDN, attrs);// 添加用户
            updateAdPwdByReplace("CN=" + newUserName + "," +  rootDN, password);// 用户密码初始化
            System.out.println("用户" + newUserName + "添加成功");
        } catch (NameAlreadyBoundException e) {
            System.err.println("用户" + newUserName + "已经存在，请修改名字！");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除一个用户
     * @param username   用户名
     * @param baseDN   用户所在节点
     */
    public static void delete(String username, String baseDN) {
        try {
            if (getUserByName(username, baseDN).trim().equals("用户不存在，请检查!!!")) {// 先判断是否存在此用户，有在删除，没有就提示不存在此用户
                System.out.println("用户" + username + "不存在，请检查!!!");
            } else {
                ldapContext.destroySubcontext("CN=" +  username + "," + baseDN);
                System.out.println("用户" + username + "删除成功");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**  查询ldapDN，查看用户是根据用户的显示名来查询的
     * @param username  用户名
     * @param baseDN    用户所在ad域节点
     * @return
     */
    public static String getUserByName(String username, String  baseDN) {
        String accountDN = " ";
        try {
            SearchControls constraints = new SearchControls();// 建立搜索
            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);// 设置搜索选项
            // String filter =  "(&(objectClass=user)(samaccountname=" +
            // username+ "))";//设置匹配项筛选,根据登录名搜索
            String filter =  "(&(objectCategory=person)(objectClass=user)(cn="
                    + username + "))";// 设置匹配项筛选，根据用户名搜索
            NamingEnumeration<SearchResult> en = ldapContext.search(baseDN,  filter,
                    constraints);
            if (!en.hasMoreElements()) {// 判断是否匹配到结果
                return accountDN = "用户不存在，请检查!!!";
            } else {
                while (en.hasMoreElements()) {
                    SearchResult rs = (SearchResult)  en.nextElement();
                    accountDN = "用户名:" + rs.getName() + ","  + baseDN;// 查询到结果，返回用户的所在节点详细信息
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return accountDN;
    }

    /**  查询ldapDN，查询登录用户信息(sAMAccountName)
     * @param loginName  登录用户名
     * @param baseDN    用户所在ad域节点
     * @return 获取用户 distinguishedName、userAccountControl等信息
     */
    public ArrayList<AdUserDto> getUserInfoByName(String loginName, String  baseDN) {
        ArrayList<AdUserDto> resultList = new ArrayList<AdUserDto>();
        try {
            // LDAP搜索过滤器类, 搜索字段"sAMAccountName", sAMAccountName=*name*模糊查询, sAMAccountName=name 精确查询
            String searchFilter = "(sAMAccountName="+ loginName +")";
            // 创建搜索控制器
            SearchControls searchCtls = new SearchControls();
            String[] returnedAtts ={"distinguishedName","description","sAMAccountName","userAccountControl"};
            searchCtls.setReturningAttributes(returnedAtts); //设置指定返回的字段，不设置则返回全部
            //  设置搜索范围 深度
            searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            // 根据设置的域节点、过滤器类和搜索控制器搜索LDAP得到结果
            NamingEnumeration<SearchResult> en = ldapContext.search(baseDN,  searchFilter, searchCtls);
            if (!en.hasMoreElements()) {// 判断是否匹配到结果
                return resultList;
            } else {
                while (en.hasMoreElements()) {
                    AdUserDto adUserDto = new AdUserDto();
                    SearchResult rs =  en.nextElement();
                    Attributes attributes = rs.getAttributes();// 得到所需的用户信息组
                    if (attributes != null) {
                        try {
                            Attribute uac = attributes.get("useraccountcontrol");  // 用户控制
                            adUserDto.setUserAccountControl(uac.get().toString());

                            Attribute account = attributes.get("samaccountname");  // 账户名
                            adUserDto.setSAMAccountName(account.get().toString());

                            Attribute userDN = attributes.get("distinguishedname");// 用户控制
                            adUserDto.setDistinguishedName(userDN.get().toString());

                            resultList.add(adUserDto);
                        } catch (NamingException e) {
                            LOGGER.info("Throw Exception : " + e.getMessage());
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultList;
    }

    /** 修改用户名
     *
     * @param userName   要修改的用户名
     * @param newName   修改之后的用户名
     * @param userDN    用户所在位置
     */
    public void renameUser(String userName, String newName,  String userDN) {
        try {
            if (getUserByName(userName, userDN).trim().equals("用户不存在，请检查!!!")) {// 先判断用户是否存在
                System.err.println("用户名不存在，请重新输入!!!");
            } else {
                ctx.rename("CN=" + userName + "," + userDN,  "CN=" + newName
                        + "," + userDN);
                System.out.println("用户名修改成功!!!");
                updateOnePersonByCn(newName, userDN);// 用户名修改后，同步用户姓，名，登录名属性值
            }
        } catch (NamingException ne) {
            ne.printStackTrace();
            System.err.println("用户名修改失败!!!");
        }
    }
    /**
     * 重置/修改密码
     * 通过直接替换密码字段里面的值
     *
     * @param userDN   用户所在位置全路径
     * @param passWord    密码
     * @return
     */
    public static Boolean updateAdPwdByReplace(String userDN, String  passWord) {
        ModificationItem[] mods = new ModificationItem[2];
        try {
            passWord = "\"" + passWord + "\"";
            byte[] newpasswordBytes =  passWord.getBytes("UTF-16LE");
            mods[0] = new  ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                    new BasicAttribute("unicodePwd",  newpasswordBytes));
            mods[1] = new  ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                    new BasicAttribute("pwdLastSet", "0"));
            ldapContext.modifyAttributes(userDN, mods);
            System.out.println("用户" + userDN + "重置密码成功");
            return true;
        } catch (Exception e) {
            System.err.println("用户" + userDN + "重置密码失败" +  e.getMessage());
            return false;
        }
    }
    /**
     * 修改用户属性信息
     * @param adUser   要修改的用户对象
     * @param BaseDN   用户所在位置
     * @throws NamingException
     */
    public void updateOnePersonAll(AdUserDto adUser, String  BaseDN)
            throws NamingException {
        if (adUser == null || adUser.getCn() == null
                || adUser.getCn().length() <= 0) {
            return;
        }
        List<ModificationItem> mList = new  ArrayList<ModificationItem>(); // 构造要修改对象的属性组
        mList.add(new  ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                new BasicAttribute("displayName",  adUser.getDisplayName()))); // 修改用户显示名
        mList.add(new  ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                new BasicAttribute("sn", adUser.getSn()))); // 修改用户姓
        mList.add(new  ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                new BasicAttribute("givenName",  adUser.getGivenName()))); // 修改用户名
        mList.add(new  ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                new BasicAttribute("description",  adUser.getDescription()))); // 修改用户描述信息
        mList.add(new ModificationItem(
                DirContext.REPLACE_ATTRIBUTE,
                new BasicAttribute("sAMAccountName",  adUser.getSAMAccountName()))); // 修改用户登录名
        if (mList.size() > 0) {
            ModificationItem[] mArray = new  ModificationItem[mList.size()];
            for (int i = 0; i < mList.size(); i++) {
                mArray[i] = mList.get(i);
            }
            ldapContext.modifyAttributes(getUserByName(adUser.getCn(), BaseDN)
                    .trim().split("\\:")[1], mArray);// 参数为：用户详细位置，修改的用户属性组
            System.out.println("用户" + adUser.getCn() + "属性修改成功");
        }
    }
    /**
     * 同步用户属性信息 ,只修改用户姓，名和登录名
     * @param username   要修改的用户名
     * @param BaseDN   用户所在位置
     * @throws NamingException
     */
    public static void updateOnePersonByCn(String username, String  BaseDN)
            throws NamingException {
        AdUserDto aduser = new AdUserDto();
        if (aduser == null || aduser.getCn() == null
                || aduser.getCn().length() <= 0) {
            System.out.println("用户名不能为空");
            return;
        }
        List<ModificationItem> mList = new  ArrayList<ModificationItem>(); // 构造要修改对象的属性组
        mList.add(new  ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                new BasicAttribute("displayName",  aduser.getDisplayName()))); // 修改用户显示名
        mList.add(new  ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                new BasicAttribute("sn", aduser.getSn()))); // 修改用户姓
        mList.add(new  ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                new BasicAttribute("givenName",  aduser.getGivenName()))); // 修改用户名
        mList.add(new ModificationItem(
                DirContext.REPLACE_ATTRIBUTE,
                new BasicAttribute("sAMAccountName",  aduser.getSAMAccountName()))); // 修改用户登录名
        if (mList.size() > 0) {
            ModificationItem[] mArray = new  ModificationItem[mList.size()];
            for (int i = 0; i < mList.size(); i++) {
                mArray[i] = mList.get(i);
            }
            ldapContext.modifyAttributes(getUserByName(aduser.getCn(), BaseDN)
                    .trim().split("\\:")[1], mArray);// 参数为：用户详细位置，修改的用户属性组
            System.out.println("用户" + username + "属性修改成功");
        }
    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        String str = "172.19.60.177";
        String res = Base64.getEncoder().encodeToString(str.getBytes("UTF-8"));

        System.out.println(res);

        String restr = new String (Base64.getDecoder().decode(res), "UTF-8");
        System.out.println(str);
        System.out.println(restr);
//        String adHost = "172.19.60.177";
//        int adNormalPort = 389;
//        String adminName = "qa";
//        String adminPassWord = "Aqumon@2050~~";
//        LdapUtil ldap = new LdapUtil();
//        LoginResp loginResp = ldap.getConnectNormal(adHost, adNormalPort, adminName, adminPassWord);
//        ldap.renameUser("test11", "test22", "DC=aqumon,DC=com");
////        LoginResp loginResp = ldap.getConnectNormal(adHost, adNormalPort, adminName, adminPassWord);
//        if (loginResp.getMsg().trim().contains("认证成功")) {
//            System.out.println("管理员登录成功");
//            List<AdUserDto> resultList =  ldap.getUserInfoByName("hefei.xiao","DC=aqumon,DC=com");
//            if (resultList.size() > 0) {
//                for (AdUserDto result: resultList) {
//                    if (result.getSAMAccountName().equals("hefei.xiao")) {
//                        ldap.getConnectNormal("172.19.60.177", 389, "hefei.xiao", "Xhf023_ok");
//                        if (loginResp.getMsg().trim().contains("认证成功")) {
//                            System.out.println("用户认证成功");
//                        } else {
//                            System.out.println("用户认证失败");
//                        }
//                    }
//                }
//            }
//            System.out.println(resultList);
//        } else {
//            System.out.println("管理员登录失败");
//        }
    }
}
